package IC.lir;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Stack;
import IC.BinaryOps;
import IC.Constants;
import IC.AST.*;
import IC.SymbolTable.*;
import IC.Types.ClassType;
import IC.Types.TypeTable;
import IC.lir.instructions.*;

/**
 * a propagating visitor class that translate the AST into LIR program.
 */
public class LIRTranslatorVisitor implements PropagatingVisitor<Register, Object> {
	
	private Program Root;
	private LIRProgram lirProgram;
	private List<Instruction> iAccumulator;
	private Stack<Label> lend;
	private Stack<Label> ltest;
	
	//C'tor
	public LIRTranslatorVisitor(Program Root) {
		this.Root = Root;
		this.lirProgram = new LIRProgram();
		this.lend = new Stack<Label>();
		this.ltest = new Stack<Label>();
	}
	
	/*
	 * LIRTranslatorVisitor Entry point - Run Translator
	 */
	public LIRProgram Run() throws Exception{
		
		//Build class layouts:
		for (ICClass c : this.Root.getClasses()) {
			
			if (LIRUtils.isLibraryClass(c.getName()))
				continue;
			
			LIRClassLayout cl;
			if (c.hasSuperClass()) {
				LIRClassLayout superCl = lirProgram.getClasses().get(c.getSuperClassName()).getClassLayout();
				cl = new LIRClassLayout(superCl);
			} 
			else 
				cl = new LIRClassLayout();

			LIRClass lirClass = new LIRClass(c.getName(), lirProgram, cl);
			lirProgram.getClasses().put(lirClass.getName(), lirClass);
			
			for (Field f : c.getFields())
				cl.addField(f.getName());
			
			for (Method m : c.getMethods()) {
				LIRMethod lirMethod = new LIRMethod(m, lirClass);
				lirClass.getMethods().add(lirMethod);
				cl.addMethod(lirMethod);
			}	
		}
		
		Root.accept(this,null);
		return lirProgram;
	}
	
	@Override
	public Object visit(Program p, Register r) throws Exception {
		for (ICClass c : p.getClasses()) {
			if (LIRUtils.isLibraryClass(c.getName()))
				continue;
			c.accept(this,new Register());
		}
		return null;
	}

	@Override
	public Object visit(ICClass c, Register r) throws Exception {
		for (LIRMethod m : lirProgram.getClasses().get(c.getName()).getMethods()) {
			iAccumulator = m.getInstructions();
			iAccumulator.add(new LabelInstruction(m.getLabel()));
			m.getAstMethod().accept(this,r);
		}
		return null;
	}

	@Override
	public Object visit(VirtualMethod m, Register r) throws Exception {
		for (Statement s : m.getStatements()) 
			s.accept(this,r);

        if (m.getType().getNodeType().equals(TypeTable.voidType)) {            
        	ReturnInstruction ri = new ReturnInstruction(DummyRegister.Inst());    
        	iAccumulator.add(ri);      
        }  
		return null;
	}

	@Override
	public Object visit(StaticMethod m, Register r) throws Exception {
		for (Statement s : m.getStatements()) 
			s.accept(this,r);
		
		if (m.getName().equals("main"))
			iAccumulator.add(new LibraryInstruction("exit", DummyRegister.Inst(), LIRConstants.IMM_ZERO));
		else 
		if (m.getType().getNodeType().equals(TypeTable.voidType))        
			iAccumulator.add(new ReturnInstruction(DummyRegister.Inst()));       
      
		return null;
	}
	
	@Override
	public Object visit(CallStatement cs, Register r) throws Exception {
		cs.getCall().accept(this, r);
		return null;
	}
	
	@Override
	public Object visit(Return rs, Register r) throws Exception {
		if (rs.hasValue()) {
			rs.getValue().accept(this, r);
			iAccumulator.add(new ReturnInstruction(r));
		} else 
			iAccumulator.add(new ReturnInstruction(DummyRegister.Inst()));
		return null;
	}
	
	@Override
	public Object visit(Assignment ass, Register r) throws Exception {
		
		ass.getAssignment().accept(this, r);
		if (ass.getVariable() instanceof ArrayLocation) {
			
			ArrayLocation loc = (ArrayLocation) ass.getVariable();
			Register arrReg = r.getSuccessor();
			Register indexReg = arrReg.getSuccessor();
			loc.getArray().accept(this, arrReg);
			loc.getIndex().accept(this, indexReg);
		
			LIRRtc.AddNullRTC(iAccumulator, arrReg);
			LIRRtc.AddArrayIndexOutOfBoundsRTC(iAccumulator, arrReg, indexReg);
			
			iAccumulator.add(new MoveArrayInstruction(arrReg, indexReg, r, false));
		} 
		else {
			VariableLocation loc = (VariableLocation) ass.getVariable();
			if (!loc.isExternal()) {
				Symbol varSymbol = loc.getScope().lookup(loc.getName(), loc.getAppearanceOrder());
				if (varSymbol.getKind().equals(SymbolKind.FIELD)) {
							
					iAccumulator.add(new MoveInstruction(new Memory(), r.getSuccessor()));
					ClassType classType = (ClassType) loc.getScope().virtualLookup(Constants.THIS).getType();
					LIRClassLayout cl = lirProgram.getClasses().get(classType.toString()).getClassLayout();
					Immediate offset = new Immediate(cl.getFieldOffset(loc.getName()));
					
					LIRRtc.AddNullRTC(iAccumulator, r.getSuccessor());
					
					iAccumulator.add(new MoveFieldInstruction(r.getSuccessor(), offset, r, false));
				} 
				else
					iAccumulator.add(new MoveInstruction(r, new Memory(varSymbol)));
			} 
			else {	
				
				loc.getLocation().accept(this, r.getSuccessor());
				LIRClassLayout cl = lirProgram.getClasses().get(loc.getLocation().getNodeType().toString()).getClassLayout();
				Immediate offset = new Immediate(cl.getFieldOffset(loc.getName()));
				
				LIRRtc.AddNullRTC(iAccumulator, r.getSuccessor());
				
				iAccumulator.add(new MoveFieldInstruction(r.getSuccessor(), offset, r, false));
			}
		}
		return null;
	}
	
	@Override
	public Object visit(StatementsBlock sb, Register r) throws Exception {
		for (Statement s : sb.getStatements())
			s.accept(this, r);
		return null;
	}
	
	@Override
	public Object visit(If ifs, Register r) throws Exception {
		Label endLabel = LIRUtils.createUniqueLabel(LIRConstants.END_LABEL);
		ifs.getCondition().accept(this, r);
		iAccumulator.add(new BinaryInstruction(LIRConstants.IMM_ZERO, r, BinaryInstructionOp.COMPARE));
		
		if (ifs.hasElse()) {
			Label falseLabel = LIRUtils.createUniqueLabel(LIRConstants.FALSE_LABEL);
			iAccumulator.add(new ConditionalJump(falseLabel, JumpCondition.TRUE));
			ifs.getOperation().accept(this, r);
			iAccumulator.add(new JumpInstruction(endLabel));
			iAccumulator.add(new LabelInstruction(falseLabel));
			ifs.getElseOperation().accept(this, r);
		} 
		else {
			iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.TRUE));
			ifs.getOperation().accept(this, r);
		}	
		iAccumulator.add(new LabelInstruction(endLabel));
		return null;
	}

	@Override
	public Object visit(While ws, Register r) throws Exception {
		Label testLabel = LIRUtils.createUniqueLabel(LIRConstants.TEST_LABEL);
		Label endLabel = LIRUtils.createUniqueLabel(LIRConstants.END_LABEL);	
		ltest.push(testLabel);
		lend.push(endLabel);
		iAccumulator.add(new LabelInstruction(testLabel));
		ws.getCondition().accept(this, r);
		iAccumulator.add(new BinaryInstruction(LIRConstants.IMM_ZERO, r, BinaryInstructionOp.COMPARE));
		iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.TRUE));
		ws.getOperation().accept(this, r);
		iAccumulator.add(new JumpInstruction(testLabel));
		iAccumulator.add(new LabelInstruction(endLabel));
		ltest.pop();
		lend.pop();
		return null;
	}
	
	@Override
	public Object visit(LocalVariable lv, Register r) throws Exception {
		if (lv.hasInitValue()) {
			lv.getInitValue().accept(this, r);
			iAccumulator.add(new MoveInstruction(r, new Memory(lv.getScope().lookup(lv.getName()))));
		}
		return null;
	}
	
	@Override
	public Object visit(ArrayLocation loc, Register r) throws Exception {

		loc.getArray().accept(this, r);
		loc.getIndex().accept(this, r.getSuccessor());
		
		LIRRtc.AddNullRTC(iAccumulator,r);
		LIRRtc.AddArrayIndexOutOfBoundsRTC(iAccumulator, r, r.getSuccessor());
	
		iAccumulator.add(new MoveArrayInstruction(r, r.getSuccessor(), r, true));
		return null;
	}
	
	@Override
	public Object visit(VariableLocation loc, Register r) throws Exception {
		if (!loc.isExternal()) {
			Symbol s = loc.getScope().lookup(loc.getName(), loc.getAppearanceOrder());
			if (s.getKind().equals(SymbolKind.FIELD)) {
				iAccumulator.add(new MoveInstruction(new Memory(), r));
				
				ClassType classType = (ClassType) loc.getScope().virtualLookup(Constants.THIS).getType();
				LIRClassLayout cl = lirProgram.getClasses().get(classType.toString()).getClassLayout();
				Immediate offset = new Immediate(cl.getFieldOffset(loc.getName()));
				
				LIRRtc.AddNullRTC(iAccumulator,r);
				
				iAccumulator.add(new MoveFieldInstruction(r, offset, r, true));
			} 
			else
				iAccumulator.add(new MoveInstruction(new Memory(s), r));
		} 
		else {
			
			loc.getLocation().accept(this, r);
			LIRClassLayout cl = lirProgram.getClasses().get(loc.getLocation().getNodeType().toString()).getClassLayout();
			Immediate offset = new Immediate(cl.getFieldOffset(loc.getName()));
			
			LIRRtc.AddNullRTC(iAccumulator,r);

			iAccumulator.add(new MoveFieldInstruction(r, offset, r, true));
		}
		return null;
	}
	
	private void Inject(CallInstruction c, Register r,LinkedHashMap<String, Symbol> params, List<Expression> args) throws Exception {
		Iterator<Symbol> it1 = params.values().iterator();
		Iterator<Expression> it12 = args.iterator();
		while (it1.hasNext() || it12.hasNext()) {
			it12.next().accept(this, r);
			c.addArgument(new Pair(new Memory(it1.next()), r));
			r = r.getSuccessor();
		}
	}
	
	@Override
	public Object visit(VirtualCall c, Register r) throws Exception {
		LIRClassLayout cl;
		ClassType ct;
		
		if (!c.isExternal()) {
			iAccumulator.add(new MoveInstruction(new Memory(), r));
			ct = (ClassType) c.getScope().virtualLookup(Constants.THIS).getType();
			cl = lirProgram.getClasses().get(ct.toString()).getClassLayout();
		} 
		else {
			c.getLocation().accept(this, r);
			ct = (ClassType) c.getLocation().getNodeType();
			cl = lirProgram.getClasses().get(ct.toString()).getClassLayout();
		}
		

		
		Symbol s = ct.getAst().getScope().virtualLookup(c.getName());
		VirtualMethodSymbolTable st = (VirtualMethodSymbolTable) s.getSymbolTableRef().getChild(c.getName());
		
		Immediate offset = new Immediate(cl.getMethodOffset(c.getName()));

		LIRRtc.AddNullRTC(iAccumulator,r);
		
		VirtualCallInstruction vc = new VirtualCallInstruction(r, offset, r);
		Inject(vc, r.getSuccessor(), st.getFormals(), c.getArguments());
		iAccumulator.add(vc);
		return null;
	}
	
	@Override
	public Object visit(StaticCall c, Register r) throws Exception {
		if (LIRUtils.isLibraryClass(c.getClassName())) {
			Register[] args = new Register[c.getArguments().size()];
			Register curr = r;
			int i = 0;
			for (Expression e : c.getArguments()) {
				e.accept(this, curr);
				args[i++] = curr;
				curr = curr.getSuccessor();
			}
			iAccumulator.add(new LibraryInstruction(c.getName(), r, args));
		} else {
			
			LIRClassLayout cl = lirProgram.getClasses().get(c.getClassName()).getClassLayout();
			LIRMethod m = cl.getLirMethod(c.getName());
			StaticCallInstruction staticCall = new StaticCallInstruction(m.getLabel(), r);
			StaticMethodSymbolTable symbolTable = (StaticMethodSymbolTable) TypeTable.getClass(c.getClassName()).getChild(c.getName());
			
			Inject(staticCall, r, symbolTable.getFormals(), c.getArguments());
			iAccumulator.add(staticCall);
		}
		return null;
	}
	
	@Override
	public Object visit(NewClass nc, Register r) {
		LIRClass lirClass = lirProgram.getClasses().get(nc.getName());
		Immediate size = new Immediate(lirClass.getClassLayout().size());
		iAccumulator.add(new LibraryInstruction("allocateObject", r, size));
		iAccumulator.add(new MoveFieldInstruction(r, LIRConstants.IMM_ZERO, lirClass.getLabel(), false));
		return null;
	}

	@Override
	public Object visit(This t, Register r) {
		iAccumulator.add(new MoveInstruction(new Memory(), r));
		return null;
	}
	
	@Override
	public Object visit(NewArray na, Register r) throws Exception {
		na.getSize().accept(this, r);
		
		LIRRtc.AddArrayNegativeSizeRTC(iAccumulator, r);
		
		iAccumulator.add(new BinaryInstruction(LIRConstants.IMM_BYTE_SIZE, r, BinaryInstructionOp.MUL));
		iAccumulator.add(new LibraryInstruction("allocateArray", r, r));
		return null;
	}

	@Override
	public Object visit(Length l, Register r) throws Exception {
		l.getArray().accept(this, r);
		LIRRtc.AddNullRTC(iAccumulator,r);
		iAccumulator.add(new ArrayLengthInstruction(r, r));
		return null;
	}

	@Override
	public Object visit(MathBinaryOp bop, Register r) throws Exception {
		
		Instruction i = null;
		Register rs = r.getSuccessor();
		
		bop.getFirstOperand().accept(this, r);
		bop.getSecondOperand().accept(this, rs);
		
		switch (bop.getOperator()) {
			case PLUS:
				if (bop.getFirstOperand().getNodeType().equals(TypeTable.stringType))
					i = new LibraryInstruction("stringCat", r, r, rs);
				else
					i = new BinaryInstruction(rs, r, BinaryInstructionOp.ADD);
				break;
			case MINUS:
				i = new BinaryInstruction(rs, r, BinaryInstructionOp.SUB);
				break;
			case MULTIPLY:
				i = new BinaryInstruction(rs, r, BinaryInstructionOp.MUL);
				break;
			case DIVIDE:
				LIRRtc.AddDivisionByZeroRTC(iAccumulator, rs);
				
				i = new BinaryInstruction(rs, r, BinaryInstructionOp.DIV);
				break;
			case MOD:
				i = new BinaryInstruction(rs, r, BinaryInstructionOp.MOD);
				break;
		}
		iAccumulator.add(i);
		return null;
	}

	@Override
	public Object visit(LogicalBinaryOp bop, Register r) throws Exception {
		bop.getFirstOperand().accept(this, r);
		Label endLabel = LIRUtils.createUniqueLabel(LIRConstants.END_LABEL);
			
		// Handle AND\OR:
		if (bop.getOperator().equals(BinaryOps.LAND) || bop.getOperator().equals(BinaryOps.LOR)) {
			Instruction compare;
			Instruction oper; 
			if (bop.getOperator().equals(BinaryOps.LAND)) {
				compare = new BinaryInstruction(LIRConstants.IMM_ZERO, r, BinaryInstructionOp.COMPARE);
				oper = new BinaryInstruction(r.getSuccessor(), r, BinaryInstructionOp.AND);
			} else {
				compare = new BinaryInstruction(LIRConstants.IMM_ONE, r, BinaryInstructionOp.COMPARE);
				oper = new BinaryInstruction(r.getSuccessor(), r, BinaryInstructionOp.OR);
			}
			iAccumulator.add(compare);	
			iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.TRUE));
			bop.getSecondOperand().accept(this, r.getSuccessor());
			iAccumulator.add(oper);
			iAccumulator.add(new LabelInstruction(endLabel));
			return null;
		}
		
		bop.getSecondOperand().accept(this, r.getSuccessor());
		Register tempResultReg = r.getSuccessor().getSuccessor();
		iAccumulator.add(new MoveInstruction(LIRConstants.IMM_ONE, tempResultReg));
		iAccumulator.add(new BinaryInstruction(r.getSuccessor(), r, BinaryInstructionOp.COMPARE));
		switch (bop.getOperator()) {
			case LT:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.L));
				break;
			case LTE:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.LE));
				break;
			case GT:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.G));
				break;
			case GTE:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.GE));
				break;
			case EQUAL:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.TRUE));
				break;
			case NEQUAL:
				iAccumulator.add(new ConditionalJump(endLabel, JumpCondition.FALSE));
				break;
		}
		iAccumulator.add(new MoveInstruction(LIRConstants.IMM_ZERO,tempResultReg));
		iAccumulator.add(new LabelInstruction(endLabel));
		iAccumulator.add(new MoveInstruction(tempResultReg, r));
		return null;
	}

	@Override
	public Object visit(MathUnaryOp up, Register r) throws Exception {
		up.getOperand().accept(this, r);
		iAccumulator.add(new UnaryInstruction(r, UnaryInstructionOp.NEG));
		return null;
	}

	@Override
	public Object visit(LogicalUnaryOp up, Register r) throws Exception {
		up.getOperand().accept(this, r);
		iAccumulator.add(new BinaryInstruction(LIRConstants.IMM_ONE, r, BinaryInstructionOp.XOR));
		return null;
	}

	@Override
	public Object visit(ExpressionBlock eb, Register r) throws Exception {
		eb.getExpression().accept(this, r);
		return null;
	}
	
	@Override
	public Object visit(Literal l, Register r) {
		Operand o = null;
		switch (l.getType()) {
		case TRUE:
			o = LIRConstants.IMM_ONE;
			break;
		case FALSE:
		case NULL:
			o = LIRConstants.IMM_ZERO;
			break;
		case INTEGER:
			o = new Immediate(new Integer((String)l.getValue()));
			break;
		case STRING:
			o = lirProgram.getStringLiteral((String)l.getValue()).getLabel();
			break;
		}
		iAccumulator.add(new MoveInstruction(o, r));
		return null;
	}
	
	@Override
	public Object visit(Break bs, Register r) {
		iAccumulator.add(new JumpInstruction(lend.peek()));
		return null;
	}

	@Override
	public Object visit(Continue cs, Register r) {
		iAccumulator.add(new JumpInstruction(ltest.peek()));
		return null;
	}
	
	@Override
	public Object visit(Field f, Register r) {
		return null;
	}
	
	@Override
	public Object visit(LibraryMethod m, Register r) {
		return null;
	}

	@Override
	public Object visit(Formal f, Register r) {
		return null;
	}

	@Override
	public Object visit(PrimitiveType t, Register r) {
		return null;
	}

	@Override
	public Object visit(UserType t, Register r) {
		return null;
	}
}
